package com.leyou.trade.service.impl;

import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leyou.auth.constants.JwtConstants;
import com.leyou.auth.utils.TokenContext;
import com.leyou.auth.utils.UserContext;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.trade.constants.PayConstants;
import com.leyou.trade.dto.OrderFormDTO;
import com.leyou.trade.entity.Order;
import com.leyou.trade.entity.OrderDetail;
import com.leyou.trade.entity.OrderLogistics;
import com.leyou.trade.enums.OrderStatus;
import com.leyou.trade.mapper.OrderMapper;
import com.leyou.trade.service.OrderService;
import com.leyou.trade.utils.PayHelper;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.AddressDTO;
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import feign.FeignException;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.management.relation.RoleUnresolved;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.leyou.common.constants.MQConstants.ExchangeConstants.ORDER_EXCHANGE_NAME;
import static com.leyou.common.constants.MQConstants.RoutingKeyConstants.EVICT_ORDER_KEY;
import static com.leyou.trade.constants.PayConstants.ORDER_NO_KEY;
import static com.leyou.trade.constants.PayConstants.TOTAL_FEE_KEY;

/**
 * @author BaiZY on 2020/9/7.
 * @version 1.0
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    private OrderDetailServiceImpl detailService;

    private OrderLogisticsServiceImpl logisticsService;

    private ItemClient itemClient;

    private UserClient userClient;

    public OrderServiceImpl(
            OrderDetailServiceImpl detailService,
            OrderLogisticsServiceImpl logisticsService,
            ItemClient itemClient, UserClient userClient) {
        this.detailService = detailService;
        this.logisticsService = logisticsService;
        this.itemClient = itemClient;
        this.userClient = userClient;
    }

    @Autowired
    private AmqpTemplate amqpTemplate;
    @Autowired
    private PayHelper payHelper;
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 创建订单
     *
     * @param orderDTO
     * @return
     */
    @Override
    @GlobalTransactional
    public Long createOrder(OrderFormDTO orderDTO, HttpServletRequest request) {

        //1.写order
        Order order = new Order();

        //1.1用户id
        Long userId = UserContext.getUser().getId();

        order.setUserId(userId);
        //1.2 金额相关信息
        Map<Long, Integer> carts = orderDTO.getCarts();
        //1.2.1 获取所有sku的id
        List<Long> idList = new ArrayList<>(carts.keySet());
        //1.2.2 查询sku
        List<SkuDTO> skuList = itemClient.querySkuByIds(idList);
        //1.2.3 计算金额的和
        long total = 0;
        for (SkuDTO sku : skuList) {
            //获取数量
            Integer num = carts.get(sku.getId());
            //计算总金额
            total += sku.getPrice() * num;
        }
        //1.2.4 填写金额数据
        order.setTotalFee(total);
        order.setPaymentType(orderDTO.getPaymentType());
        order.setPostFee(0L);
        order.setActualFee(total + order.getPostFee());

        //1.3订单状态初始化
        order.setStatus(1);

        //1.4 把订单写入数据库
        boolean success = save(order);

        if (!success) {
            throw new LyException(500, "订单创建失败!");
        }

        // 2.写OrderDetail
        // 2.1.定义一个OrderDetail的集合
        List<OrderDetail> details = new ArrayList<>();
        // 2.2.遍历sku集合，转为detail
        for (SkuDTO sku : skuList) {
            // 2.2.1.商品数量
            int num = carts.get(sku.getId());
            // 2.2.2.组装OrderDetail
            OrderDetail detail = new OrderDetail();
            detail.setOrderId(order.getOrderId());
            detail.setImage(StringUtils.substringBefore(sku.getImages(), ","));
            detail.setNum(num);
            detail.setSkuId(sku.getId());
            detail.setSpec(sku.getSpecialSpec());
            detail.setPrice(sku.getPrice());
            detail.setTitle(sku.getTitle());
            // 2.2.3.装入detail集合
            details.add(detail);
        }
        // 2.3.批量新增
        success = detailService.saveBatch(details);
        if (!success) {
            throw new LyException(500, "订单创建失败！");
        }

        //从cookie中取出jwt存入TokenContext
        TokenContext.setToken(CookieUtils.getCookieValue(request, JwtConstants.COOKIE_NAME));


        //3.写入OrderLogistics
        //3.1 查询收货地址
        AddressDTO address = userClient.queryAddressById(orderDTO.getAddressId());
        //3.2 填写物流信息
        OrderLogistics logistics = address.toEntity(OrderLogistics.class);
        logistics.setOrderId(order.getOrderId());
        //3.3 写入数据库
        success = logisticsService.save(logistics);

        if (!success) {
            throw new LyException(500, "创建订单失败!");
        }

        //4.减库存
        try {
            itemClient.deductStock(carts);
        } catch (FeignException e) {
            throw new LyException(e.status(), e.getMessage());
        }

        amqpTemplate.convertAndSend(ORDER_EXCHANGE_NAME, EVICT_ORDER_KEY, order.getOrderId());

        //返回丁订单编号
        return order.getOrderId();
    }

    /**
     * 根据订单编号生成支付链接
     *
     * @param orderId
     * @return
     */
    @Override
    public String getPayUrl(Long orderId) {
        //1.根据订单id查询订单
        Order order = getById(orderId);

        //1.1 判断订单是否存在
        if (order == null) {
            throw new LyException(400, "订单编号错误,订单不存在");
        }
        //1.2 判断订单状态是否付款
        if (order.getStatus() != 1) {
            //1.2.1 订单已经关闭,无需再次获取支付链接
            throw new LyException(400, "订单已经关闭或者支付!");
        }

        // 从redis中获取payUrl
        String payUrl = redisTemplate.opsForValue().get("PAY_URL_" + orderId);
        if (payUrl == null) {
            //2.获取订单金额
            Long actualFee = order.getActualFee();

            //3. 统一下单,获取支付链接
            payUrl = payHelper.getPayUrl(orderId, 1L /*actualFee*/, PayConstants.ORDER_DESC);
        }

        redisTemplate.opsForValue().set("PAY_URL_" + orderId, payUrl, 2, TimeUnit.HOURS);

        //4.返回支付链接
        return payUrl;
    }

    @Override
    public void handleWxNotify(Map<String, String> data) {
        //1.业务表示校验
        payHelper.checkResultCode(data);
        //2.签名校验
        payHelper.checkResponseSignature(data);

        //3. 订单状态校验(保证幂等,防止重复通知
        String outTradeNo = data.get(ORDER_NO_KEY);
        String totalFee = data.get(TOTAL_FEE_KEY);

        if (StringUtils.isBlank(outTradeNo) || StringUtils.isBlank(totalFee)) {
            //数据有误
            throw new RuntimeException("响应数据有误,订单金额或者编号为空!");
        }
        Long orderId = Long.valueOf(outTradeNo);
        Order order = getById(orderId);
        if (!order.getStatus().equals(1)) {
            //说明订单已经支付过了,属于重复通知,直接返回
            return;
        }

        //4.订单金额校验
        Long total = Long.valueOf(totalFee);
        if (!total.equals(1L/*order.getActualFee()*/)) {
            throw new RuntimeException("订单金额有误,我要报警了!");
        }

        //5.修改订单状态,更新状态和支付时间两个字段
        update().set("status", 2).
                set("pay_time", null)
                .eq("order_id", orderId)
                .eq("status", 1)
                .update();

        log.info("处理微信支付通知成功!{}", data);
    }

    @Override
    public Integer queryOrderStatus(Long orderId) {
        //查询订单
        Order order = getById(orderId);

        //判断是否存在
        if (order == null) {
            throw new LyException(400, "订单不存在");
        }
        return order.getStatus();
    }

    @Override
    public Integer activeQueryOrderStatus(Long orderId) {
        Integer status = 0;
        //查询订单
        Map<String, String> resp = payHelper.queryOrderStatus(orderId);
        try {
            if (!CollectionUtils.isEmpty(resp)) {
                String returnCode = resp.get("return_code");
                if (returnCode.equals("SUCCESS")) {
                    String resultCode = resp.get("result_code");
                    if (resultCode.equals("SUCCESS")) {
                        String orderStatus = resp.get("out_trade_no");
                        if (orderStatus.equals("SUCCESS")) {
                            status = 2;
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new LyException(400, "订单不存在,或者未支付");
        }
        return status;
    }

    @Override
    @Transactional
    public void evictOrderIfNecessary(Long orderId) {
        // 1.查询订单
        Order order = getById(orderId);
        if(order == null){
            // 订单不存在，无需处理
            return;
        }
        // 2.判断订单是否支付
        if(order.getStatus() != 1){
            // 订单已处理，无需重复处理
            return;
        }
        // 3.如果未支付，需要关闭订单
        boolean boo = update().set("status", OrderStatus.CLOSED.getValue())
                .set("close_time", new Date())
                .eq("order_id", orderId)
                // 通过乐观锁进一步保证幂等效果
                .eq("status", OrderStatus.INIT.getValue())
                // 执行update
                .update();
        if(!boo){
            // 更新失败，订单状态已经改变，无需处理
            return;
        }
        log.info("已关闭超时未支付订单：{}", orderId);
        // 4.查询OrderDetail
        List<OrderDetail> details = detailService.query().eq("order_id", orderId).list();

        // 5.获取商品及商品数量信息
        Map<Long, Integer> skuMap = new HashMap<>();
        // 得到其中的商品和数量信息
        for (OrderDetail detail : details) {
            skuMap.put(detail.getSkuId(), detail.getNum());
        }
    /*Map<Long, Integer> map = details.stream()
        .collect(Collectors.toMap(OrderDetail::getSkuId, OrderDetail::getNum));*/

        // 6.恢复库存
        itemClient.plusStock(skuMap);
    }

}
